#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

/*
 * 易受攻击代码 (Vnlnerable Code)
 */

#define SIZE 16

int main(int argc, char* argv[])
{

 int fd = open("./inp_file", O_RDONLY); /* [1] */
 if(fd == -1) {
 printf("File open error\n");
 fflush(stdout);
 exit(-1);
 }

 if(strlen(argv[1])>1020) { /* [2] */
 printf("Buffer Overflow Attempt. Exiting...\n");
 exit(-2);
 }

 char* tmp = malloc(20-4); /* [3] */
 char* p = malloc(1024-4); /* [4] */
 char* p2 = malloc(1024-4); /* [5] */
 char* p3 = malloc(1024-4); /* [6] */

 read(fd,tmp,SIZE); /* [7] */
 strcpy(p2,argv[1]); /* [8] */

 free(p); /* [9] */
}



#if 0

echo 0 > /proc/sys/kernel/randomize_va_space
sudo chown root uaf-05-stack
sudo chgrp root uaf-05-stack
sudo chmod +s uaf-05-stack

What is off-by-one bug?

正如本文所述，将源字符串复制到目标缓冲区可能会导致以下情况之一
源字符串长度等于目标缓冲区长度。
当源字符串长度等于目标缓冲区长度时，将在目标缓冲区上方复制一个NULL字节。这里，由于目标缓冲区位于堆中，因此单个NULL字节可能会覆盖下一个块的块头，这可能会导致任意代码执行。
回顾：正如本文所述，堆段根据用户堆内存请求被划分为多个块。每个块都有自己的块头（由malloc_chunk表示）。结构malloc_chunk包含以下四个元素：
prev_size–如果前一个块是空闲的，则此字段包含前一个区块的大小。否则，如果分配了前一块，则此字段包含前一块的用户数据。
size：此字段包含此分配块的大小。此字段的最后3位包含标志信息。
PREV_NUSE（P）-在分配前一个块时设置此位。
IS_MMAPPED（M）-当块为mmap’d时，设置此位。
NON_MAIN_ARENA（N）-当该块属于线程竞技场时，设置此位。
fd–指向同一bin中的下一个块。
bk–指向同一bin中的前一个块。

NOTE: ASLR is turned off for our demo purposes. In case if you want to want to bypass ASLR too use information leakage bug or brute force technique as described in this post.

Line [2] and [8] of the above vulnerable code is where the heap based off-by-one overflow could occur. Destination buffer length is 1020 and hence source string of length 1020 bytes could lead to arbitrary code execution.

How arbitrary code execution is achieved?

Arbitrary code execution is achieved when a single null byte overwrites the chunk header of next chunk (‘p3’). When a chunk of size 1020 bytes (‘p2’) gets overflown by a single byte, next chunk (‘p3’) header’s size’s least significant byte gets overwritten with NULL byte and not prev_size’s least significant byte.

Why LSB of size gets overwritten instead of prev_size’s LSB?

checked_request2size converts user requested size into usable size (internal representation size) since some extra space is needed for storing malloc_chunk and also for alignment purposes. Conversion takes place in such a way that last 3 bits of usable size is never set and hence its used for storing flag informations P, M and N.

Thus when malloc(1020) gets executed in our vulnerable code, user request size of 1020 bytes gets converted to ((1020 + 4 + 7) & ~7) 1024 bytes (internal representation size) . Overhead for an allocated chunk of 1020 bytes is only 4 bytes!! But for an allocated chunk we need chunk header of size 8 bytes, inorder to store prev_size and size informations. Thus first 8 bytes of the1024 byte chunk will be used for chunk header, but now we are left with only 1016 (1024-8) bytes for user data instead of 1020 bytes. But as said above in prev_size definition, if previous chunk (‘p2’) is allocated, chunk’s (‘p3’) prev_size field contains user data. Thus prev_size of the chunk (‘p3’) located next to this allocated 1024 byte chunk (‘p2’) contains the remaining 4 bytes of user data!! This is the reason why LSB of size gets overwritten with single NULL byte instead of prev_size!!

注意：ASLR已关闭，用于我们的演示目的。如果你也想绕过ASLR，请使用本文所述的信息泄露漏洞或暴力破解技术。
上述易受攻击代码的第[2]行和第[8]行是基于一个溢出的堆可能发生的地方。目标缓冲区长度为1020，因此长度为1020字节的源字符串可能导致任意代码执行。
如何实现任意代码执行？
当一个空字节覆盖下一个块（'p3'）的块头时，可以实现任意代码执行。当大小为1020字节（'p2'）的块被单个字节溢出时，下一个块（'p3'）标头大小的最低有效字节将被NULL字节覆盖，而不是prev_size的最低有效字符。
为什么大小的LSB会被覆盖，而不是prev_size的LSB？
checked_request2size将用户请求的大小转换为可用大小（内部表示大小），因为存储malloc_chunk和对齐需要一些额外的空间。转换以这样的方式进行，即永远不会设置可用大小的最后3位，因此它用于存储标志信息P、M和N。
因此，当在易受攻击的代码中执行malloc（1020）时，1020字节的用户请求大小将转换为（（1020+4+7）&~7）1024字节（内部表示大小）。分配的1020字节块的开销仅为4字节！！但是对于分配的块，我们需要大小为8字节的块头，以便存储prev_size和大小信息。因此，1024字节块的前8个字节将用于块头，但现在我们只剩下1016（1024-8）个字节用于用户数据，而不是1020个字节。但正如上文在prev_size定义中所述，如果分配了前一个块（'p2'），则块（'p3'）的prev_sice字段包含用户数据。因此，位于该分配的1024字节块（'p2'）旁边的块（'p3'）的prev_size包含剩余的4个字节的用户数据！！这就是为什么大小的LSB被单个NULL字节而不是prev_size覆盖的原因！！

#endif
